use crate::cache::AppCache;
use crate::errors::{AppError, Result};
use crate::models::User;
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use sea_orm::DatabaseConnection;
use uuid::Uuid;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone)]
pub struct AuthService {
    pub jwt_secret: String,
    pub cache: AppCache,
    pub db: DatabaseConnection,
    pub oidc_issuer: String,
    pub oidc_client_id: String,
}

impl AuthService {
    pub fn new(
        db: DatabaseConnection,
        cache: AppCache,
        oidc_issuer: String,
        oidc_client_id: String,
        jwt_secret: String,
    ) -> Result<Self> {
        Ok(Self {
            jwt_secret,
            cache,
            db,
            oidc_issuer,
            oidc_client_id,
        })
    }

    pub async fn get_or_cache_user(&self, openid: &str) -> Result<User> {
        // Try to get from cache first
        if let Some(user) = self.cache.get_user(openid).await? {
            return Ok(user);
        }

        // If not in cache, fetch from database
        let user = self.fetch_user_from_db(openid).await?;
        
        // Cache the user
        self.cache.set_user(openid.to_string(), user.clone()).await?;
        
        Ok(user)
    }

    async fn fetch_user_from_db(&self, openid: &str) -> Result<User> {
        use crate::entities::user;
        use sea_orm::*;

        let user = user::Entity::find()
            .filter(user::Column::Openid.eq(openid))
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound {
                entity: "User".to_string(),
                id: Uuid::new_v4(), // This is a placeholder, should use openid
            })?;

        Ok(User {
            id: user.id,
            username: user.username,
            avatar: user.avatar,
            openid: user.openid,
        })
    }

    pub async fn create_user_if_not_exists(&self, openid: &str, username: &str, avatar: Option<String>) -> Result<User> {
        use crate::entities::user;
        use sea_orm::*;

        // Check if user already exists
        if let Some(user) = self.fetch_user_from_db(openid).await.ok() {
            return Ok(user);
        }

        // Create new user
        let new_user = user::ActiveModel {
            id: Set(Uuid::new_v4()),
            username: Set(username.to_string()),
            avatar: Set(avatar),
            openid: Set(openid.to_string()),
            created_at: Set(chrono::Utc::now()),
            updated_at: Set(chrono::Utc::now()),
        };

        let user = new_user.insert(&self.db).await?;

        let user_model = User {
            id: user.id,
            username: user.username,
            avatar: user.avatar,
            openid: user.openid,
        };

        // Cache the new user
        self.cache.set_user(openid.to_string(), user_model.clone()).await?;

        Ok(user_model)
    }
}

// JWT Claims structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Claims {
    pub sub: String,
    pub iss: String,
    pub aud: String,
    pub exp: usize,
    pub iat: usize,
    pub openid: String,
}

impl AuthService {
    pub fn verify_jwt(&self, token: &str) -> Result<Claims> {
        let token_data = decode::<Claims>(
            token,
            &DecodingKey::from_secret(self.jwt_secret.as_ref()),
            &Validation::default(),
        )?;
        Ok(token_data.claims)
    }

    pub fn generate_jwt(&self, claims: &Claims) -> Result<String> {
        let token = encode(
            &Header::default(),
            claims,
            &EncodingKey::from_secret(self.jwt_secret.as_ref()),
        )?;
        Ok(token)
    }
}